package com.harvest.scientific.ladder.page.camerax

import android.Manifest
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.YuvImage
import android.os.Build
import android.util.Log
import androidx.camera.core.AspectRatio
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraState
import androidx.camera.core.FocusMeteringAction
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.OnImageSavedCallback
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.core.UseCaseGroup
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.google.common.util.concurrent.ListenableFuture
import com.harvest.scientific.ladder.ext.asLifecycleOwner
import com.harvest.scientific.ladder.ext.getScreenHeight
import com.harvest.scientific.ladder.ext.getScreenWidth
import java.io.ByteArrayOutputStream
import java.io.File
import java.nio.ByteBuffer
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import kotlin.math.abs


class CameraxController {

    companion object {
        const val TAG = "CameraxController"
        const val RATIO_4_3_VALUE = 4.0 / 3.0
        const val RATIO_16_9_VALUE = 16.0 / 9.0
    }

    private lateinit var mContext: Context
    private lateinit var mUIPreviewView: PreviewView
    private lateinit var mCameraExecutor: Executor
    private lateinit var mCameraSelector: CameraSelector
    private lateinit var mCameraPreviewUseCase: Preview
    private lateinit var mImageCaptureUseCase: ImageCapture

    private var mCameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
    private var mCameraProvider: ProcessCameraProvider? = null
    private var mCamera: Camera? = null
    private var mLensFacing = 0
    private var mIsCameraPreview = false
    private var mIsTaskPicture = true

    private val RECORDING_PERMISSION_ABOVE_33 = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.READ_MEDIA_VIDEO,
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_AUDIO
    )

    private val RECORDING_PERMISSION_UNDER_33 = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )

    private val TAKE_PIC_PERMISSION_ABOVE_33 = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.READ_MEDIA_VIDEO,
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_AUDIO
    )

    private val TAKE_PIC_PERMISSION_UNDER_33 = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    )

    fun initCameraX(
        context: Context,
        previewView: PreviewView,
    ) {
        kotlin.runCatching {
            this.mContext = context
            this.mUIPreviewView = previewView
            mCameraExecutor = ContextCompat.getMainExecutor(mContext)
            mCameraProviderFuture = ProcessCameraProvider.getInstance(context)
            mCameraProviderFuture?.addListener({
                mCameraProvider = mCameraProviderFuture?.get()
                mLensFacing = lensFacing
                bindCameraImageUseCases()
            }, mCameraExecutor) ?: kotlin.run {
            }
        }.onFailure {
            Log.d(TAG, "initCameraX ${it.stackTraceToString()}")
        }
    }


    private fun bindCameraImageUseCases() {
        kotlin.runCatching {
            buildCameraSelector()
            buildPreviewUseCase()
            buildImageCaptureUseCase()
            val useCases = UseCaseGroup.Builder().also {
                it.addUseCase(mCameraPreviewUseCase)
                it.addUseCase(mImageCaptureUseCase)
            }.build()
            mCameraProvider?.unbindAll()
            removeCameraState(mCamera?.cameraInfo)
            mCamera = mCameraProvider?.bindToLifecycle(
                mContext as LifecycleOwner,
                mCameraSelector,
                useCases
            )
            observeCameraState(mCamera?.cameraInfo)
        }.onFailure {
            Log.d(TAG, "bindCameraImageUseCases ${it.stackTraceToString()}")
        }
    }

    private fun aspectRatio(width: Int, height: Int): Int {
        val aspect = width.coerceAtLeast(height).toDouble()
        val previewRatio = aspect / width.coerceAtMost(height)
        return if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)
        ) {
            AspectRatio.RATIO_4_3
        } else {
            AspectRatio.RATIO_16_9
        }
    }

    /**
     * 构建摄像头选择器
     */
    private fun buildCameraSelector() {
        mCameraSelector = CameraSelector.Builder().requireLensFacing(mLensFacing).build()
    }

    /**
     * 构建-PreviewUseCase
     */
    private fun buildPreviewUseCase() {
        kotlin.runCatching {
            mCameraPreviewUseCase = Preview.Builder()
                .setTargetAspectRatio(
                    aspectRatio(
                        getScreenWidth(),
                        getScreenHeight()
                    )
                )
                .build()
                .also {
                    it.setSurfaceProvider(mUIPreviewView.surfaceProvider)
                }
        }.onFailure {
            Log.d(TAG, "buildPreviewUseCase ${it.stackTraceToString()}")
        }
    }

    private fun buildImageCaptureUseCase() {
        kotlin.runCatching {
            mImageCaptureUseCase = ImageCapture.Builder()
                .setTargetAspectRatio(
                    aspectRatio(
                        getScreenWidth(),
                        getScreenHeight()
                    )
                )
                .setFlashMode(ImageCapture.FLASH_MODE_OFF)
                .build()
        }.onFailure {
            Log.d(TAG, "buildImageCaptureUseCase ${it.stackTraceToString()}")
        }
    }

    private fun stopPreview() {
        kotlin.runCatching {
            mCameraProvider?.unbindAll()
        }.onFailure {
            Log.d(TAG, "stopPreview ${it.stackTraceToString()}")
        }
    }

    private val lensFacing: Int
        private get() {
            if (hasBackCamera()) {
                return CameraSelector.LENS_FACING_BACK
            }
            if (hasFrontCamera()) {
                return CameraSelector.LENS_FACING_FRONT
            }
            return -1
        }

    private fun hasBackCamera(): Boolean {
        var hasBackCamera = false
        mCameraProvider?.let { provider ->
            kotlin.runCatching {
                hasBackCamera = provider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)
            }.onFailure {
                hasBackCamera = false
            }
        } ?: kotlin.run {
            hasBackCamera = false
        }
        return hasBackCamera
    }

    private fun hasFrontCamera(): Boolean {
        var hasFrontCamera = false
        mCameraProvider?.let { provider ->
            kotlin.runCatching {
                hasFrontCamera = provider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA)
            }.onFailure {
                hasFrontCamera = false
            }
        } ?: kotlin.run {
            hasFrontCamera = false
        }
        return hasFrontCamera
    }

    private fun getPermissionTips(): String {
        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) "无法拍摄，请到“设置-应用程序”中，打开您的相机和存储权限" else "无法打开相册,请到\"设置>应用>跨声>权限\"中打开相机、音乐与音频、照片与视频权限"
    }

    fun takePicture(filePath: String?, callback: OnImageSavedCallback) {
        kotlin.runCatching {
            if (filePath.isNullOrBlank()) {
                return
            }
            mImageCaptureUseCase.takePicture(
                ImageCapture.OutputFileOptions.Builder(File(filePath)).build(),
                mCameraExecutor,
                callback
            )
        }.onFailure {
        }
    }

    fun setLinearZoom(scale: Float) {
        kotlin.runCatching {
            mCamera?.cameraControl?.setLinearZoom(scale)
        }.onFailure {
        }
    }

    fun setTapToFocus(x: Float, y: Float) {
        kotlin.runCatching {
            val meteringPointFactory = mUIPreviewView.meteringPointFactory
            val meteringPoint = meteringPointFactory.createPoint(x, y)
            val action = FocusMeteringAction.Builder(meteringPoint, FocusMeteringAction.FLAG_AF)
                .setAutoCancelDuration(4, TimeUnit.SECONDS)
                .build()
            mCamera?.cameraControl?.startFocusAndMetering(action)
        }.onFailure {
        }
    }

    fun setIsCameraPreview(isCameraPreview: Boolean) {
        this.mIsCameraPreview = isCameraPreview
    }

    fun isCameraPreView(): Boolean = mIsCameraPreview

    private fun imageToBitmap(imageProxy: ImageProxy): Bitmap? {
        var bitmap: Bitmap? = null
        kotlin.runCatching {
            if (imageProxy.format == PixelFormat.RGBA_8888) {
                val buffer: ByteBuffer = imageProxy.planes[0].buffer
                val tempBitmap = Bitmap.createBitmap(
                    imageProxy.width,
                    imageProxy.height,
                    Bitmap.Config.ARGB_8888,
                )
                buffer.rewind()
                tempBitmap?.copyPixelsFromBuffer(buffer)

                val matrix = Matrix()
                matrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat())
                bitmap = Bitmap.createBitmap(
                    tempBitmap,
                    0,
                    0,
                    tempBitmap.width,
                    tempBitmap.height,
                    matrix,
                    true
                )
            } else {
                val planes: Array<ImageProxy.PlaneProxy> = imageProxy.planes
                val yBuffer: ByteBuffer = planes[0].buffer
                val uBuffer: ByteBuffer = planes[1].buffer
                val vBuffer: ByteBuffer = planes[2].buffer
                val ySize: Int = yBuffer.remaining()
                val uSize: Int = uBuffer.remaining()
                val vSize: Int = vBuffer.remaining()
                val nv21 = ByteArray(ySize + uSize + vSize)
                yBuffer.get(nv21, 0, ySize)
                vBuffer.get(nv21, ySize, vSize)
                uBuffer.get(nv21, ySize + vSize, uSize)
                val yuvImage =
                    YuvImage(nv21, ImageFormat.NV21, imageProxy.width, imageProxy.height, null)
                val out = ByteArrayOutputStream()
                yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out)
                val imageBytes = out.toByteArray()
                val opt = BitmapFactory.Options()
                opt.inPreferredConfig = Bitmap.Config.ARGB_8888
                val tempBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, opt)

                val matrix = Matrix()
                matrix.postRotate(imageProxy.imageInfo.rotationDegrees.toFloat())
                bitmap = Bitmap.createBitmap(
                    tempBitmap,
                    0,
                    0,
                    tempBitmap.width,
                    tempBitmap.height,
                    matrix,
                    true
                )
            }
        }.onFailure {
        }
        return bitmap
    }

    fun onDestroy() {
        kotlin.runCatching {
            stopPreview()
            removeCameraState(mCamera?.cameraInfo)
        }.onFailure {
        }
    }

    private fun observeCameraState(cameraInfo: CameraInfo?) {
        kotlin.runCatching {
            if (cameraInfo == null) {
                return
            }
            cameraInfo.cameraState?.observe(mContext.asLifecycleOwner) { cameraState ->
                when (cameraState.type) {
                    CameraState.Type.PENDING_OPEN -> {
                        mIsCameraPreview = false
                    }

                    CameraState.Type.OPENING -> {
                        mIsCameraPreview = false
                    }

                    CameraState.Type.OPEN -> {
                        mIsCameraPreview = true
                    }

                    CameraState.Type.CLOSING -> {
                        mIsCameraPreview = false
                    }

                    CameraState.Type.CLOSED -> {
                        mIsCameraPreview = false
                    }
                }
            }
        }.onFailure {
        }
    }

    private fun removeCameraState(cameraInfo: CameraInfo?) {
        kotlin.runCatching {
            cameraInfo?.cameraState?.removeObservers(mContext.asLifecycleOwner)
        }.onFailure {
        }
    }
}